package com.cocotingo.snail.template;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.cocotingo.snail.template.annotations.TemplateCallback;
import com.cocotingo.snail.template.annotations.TemplateVariable;

class ReflectionAgent {
	
	private static ReflectionAgent instance;
	
	static ReflectionAgent getInstance() {
		if (instance == null) instance = new ReflectionAgent();
		return instance;
	}
	
	private class ClassInfo {
		
		Class<?> clazz;
		Method[] methods;
		final Map<String, Method> setters;
		List<Field> template_vars;
		List<Method> template_callbacks;
		
		ClassInfo(Class<?> clazz) {
			this.clazz = clazz;
			this.methods = null;
			this.setters = new HashMap<String, Method>();
			this.template_vars = null;
			this.template_callbacks = null;
		}
		
		Method findSetter(String setterName) {
			if (!setters.containsKey(setterName)) {
				if (methods == null) {
					methods = clazz.getMethods();
				}
				Method setter = null;
				for (Method method : methods) {
					if (method.getName().equals(setterName)) {
						Class<?>[] paraClass = method.getParameterTypes();
						if (paraClass.length == 1) {
							if (TemplateLoader.canAdaptValueType(paraClass[0])) {
								setter = method;
								break;
							}
						}
					}
				}
				setters.put(setterName, setter);
			}
			return setters.get(setterName);
		}
		
		void clearSetterCache() {
			setters.clear();
		}
		
		Iterable<Field> getAllTemplateVariables() {
			if (template_vars == null) {
				template_vars = new ArrayList<Field>();
				Field[] fields = clazz.getFields();
				for (Field field : fields) {
					if (field.isAnnotationPresent(TemplateVariable.class)) {
						template_vars.add(field);
					}
				}
			}
			return template_vars;
		}
		
		Iterable<Method> getAllTemplateCallbacks() {
			if (template_callbacks == null) {
				template_callbacks = new ArrayList<Method>();
				Method[] methods = clazz.getMethods();
				for (Method method : methods) {
					if (method.isAnnotationPresent(TemplateCallback.class)) {
						template_callbacks.add(method);
					}
				}
			}
			return template_callbacks;
		}
		
	}
	
	private final Map<String, String> setterNames;
	private final Map<Class<?>, ClassInfo> classInfos;
	
	private ReflectionAgent() {
		setterNames = new HashMap<String, String>();
		classInfos = new HashMap<Class<?>, ClassInfo>();
	}
	
	public Method findSetter(Class<?> clazz, String attributeName) {
		if (!setterNames.containsKey(attributeName)) {
			StringBuffer buf = new StringBuffer("set");
			buf.append(Character.toUpperCase(attributeName.charAt(0)));
			for (int i = 1; i < attributeName.length(); i++) {
				buf.append(attributeName.charAt(i));
			}
			setterNames.put(attributeName, buf.toString());
			
		}
		String setterName = setterNames.get(attributeName);
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.findSetter(setterName);
	}
	
	public Iterable<Field> getAllTemplateVariables(Class<?> clazz) {
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.getAllTemplateVariables();
	}
	
	public Iterable<Method> getAllTemplateCallbacks(Class<?> clazz) {
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.getAllTemplateCallbacks();
	}
	
	public void clearSetterCache() {
		for (ClassInfo info : classInfos.values()) {
			info.clearSetterCache();
		}
	}

}
